<?php

/**
 * 数据库操作，PDO类 
 * 
 * @package Db
 * @author Chengxuan <chengxuan@leju.sina.com.cn>
 * @version #Id:db_pdo.php 2010-4-27 下午06:03:46#
 */
class Cls_Db_Pdo extends Cls_Db_Base {
	/**
	 * 主库连接对象
	 * @var PDO
	 */
	protected $pdo_master;
	
	/**
	 * 从库连接对象
	 * @var PDO
	 */
	protected $pdo_slave;
	
	/**
	 * 连接数据库
	 * @param string $host		主机地址
	 * @param string $username	用户名
	 * @param string $password	密码
	 * @param string $database	数据库名
	 * @param string $port		数据库端口
	 */
	public function connect($host, $username, $password, $database, $port=3306)
	{
		try
		{
			$dsn = "mysql:dbname={$database};host={$host};port={$port}";
		    $pdo = new PDO($dsn, $username, $password);
			$sql_init = "SET NAMES 'utf8';";
//			$sql_init .= 'SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT;';
//			$sql_init .= 'SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS;';
//			$sql_init .= 'SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION;';
			
			$pdo->query($sql_init);
			$pdo->query($sql_init);
			$pdo->query($sql_init);
			$pdo->query($sql_init);
		    return $pdo;
		}
		catch (PDOException $e)
		{
			$this->throwError($e->getMessage());
		}
	}
	
	/**
	 * 测试连接是否存在，如果不存在，重新连接
	 * @param string/int $server	根据SQL语句自动选择或强制指定数据库
	 * @return PDO
	 */
	public function ping($server)
	{
		!is_int($server) && $server = $this->getServer($server);
		if($server == self::CONN_MASTER)
		{
			$pdo = & $this->pdo_master;
		}
		else
		{
			$pdo = & $this->pdo_slave;
		}

		
		if($pdo)
		{
			$status = $pdo->getAttribute(PDO::ATTR_SERVER_INFO);
		}
		
		if(!$pdo || $status == 'MySQL server has gone away')
		{
			$conf = $this->getConf($server);
			$pdo = $this->connect($conf['host'], $conf['username'], $conf['password'], $conf['database'], $conf['port']);
		}
		
		return $pdo;
	}
	
	/**
	 * 查询一句语句，反回查询句柄
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param boolean $is_master		是否强制使用主库
	 * @return PDOStatement
	 */
	public function query($sql, $param=null, $is_master=false)
	{
		$this->sql = $this->makeSQL($sql, $param);
		$server = ($is_master || $this->getServer($this->sql) == self::CONN_MASTER) ? self::CONN_MASTER : self::CONN_SLAVE;
		
		$pdo = $this->ping($server);
		if(!$pdo instanceof PDO) return false;

		$query = $pdo->query($this->sql);
		if(!$query)	$this->error($pdo);

		return $query;
	}
	
	
	/**
	 * 查询一条语句，返回影响的数据行数
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param boolean $is_master		是否强制使用主库
	 * @return int
	 */
	public function execute($sql, $param=null, $is_master=false)
	{
		$this->sql = $this->makeSQL($sql, $param);
		$server = ($is_master || $this->getServer($this->sql) == self::CONN_MASTER) ? self::CONN_MASTER : self::CONN_SLAVE;

		$pdo = $this->ping($server);
		if(!$pdo instanceof PDO) return false;
		
		$result = $pdo->exec($this->sql);
		if($result === false)	$this->error($pdo);
		return $result;
	}
	
	/**
	 * 取得一个数据
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param null/string $column	指定返回字段
	 * @param boolean $is_master		是否强制使用主库
	 * @return string/boolean
	 */
	public function fetchOne($sql, $param=null, $column=null, $is_master=false)
	{		
		if(!$column || is_numeric($column)) {
			$query = $this->query($sql, $param, $is_master);
			return $query ? $query->fetchColumn((int)$column) : $query;
		}
		else
		{
			$result = $this->fetchRow($sql, $param, $is_master, self::FETCH_ASSOC);
			return isset($result[$column]) ? $result[$column] : false;
		}
	}

	/**
	 * 取得一行数据
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param boolean $is_master		是否强制使用主库
	 * @param int $fetch_type		指定返回数据格式(Cls_Db_Base::FETCH_*)
	 * @return array/boolean
	 */
	public function fetchRow($sql, $param=null, $is_master=false, $fetch_type=self::FETCH_ASSOC)
	{
		$query = $this->query($sql, $param, $is_master);
		return $query ? $query->fetch($this->F($fetch_type)) : $query;
	}	
	
	/**
	 * 取得一列数据
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param null/string $column	指定返回字段
	 * @param boolean $is_master		是否强制使用主库
	 * @return array
	 */
	public function fetchCol($sql, $param=null, $column=null, $is_master=false)
	{
		if(!$column || is_numeric($column))
		{
			$fetch_type = self::FETCH_NUM;
			$column = (int)$column;
		}
		else
		{
			$fetch_type = self::FETCH_ASSOC;
		}
		$result = $this->fetchAll($sql, $param, $is_master, $fetch_type);
		return $result ? Cls_Array::getCols($result, $column) : $result;
	}	
	
	/**
	 * 取得所有数据
	 * @param string $sql			SQL语句
	 * @param array $param			变量参数
	 * @param boolean $is_master		是否强制使用主库
	 * @param int $fetch_type		指定返回数据格式(Cls_Db_Base::FETCH_*)
	 * @return array
	 */
	public function fetchAll($sql, $param=null, $is_master=false, $fetch_type=self::FETCH_ASSOC)
	{
		$query = $this->query($sql, $param, $is_master);
		return $query ? $query->fetchAll($this->F($fetch_type)) : $query;
	}
	
	/**
	 * 取得SELECT查询的数据行数
	 * @param PDOStatement $statement
	 * @return int/boolean
	 */
	public function numRows($statement)
	{
		if(!$statement instanceof PDOStatement)
		{
			return false;
		}
		
		return $statement->rowCount();
	}
	
	
	/**
	 * 转换类型，将基类的数据类型转换为PDO的
	 * @param int $self_fetch
	 * @return int
	 */
	protected function F($self_fetch) {
		static $conf = array(
			self::FETCH_ASSOC	=> PDO::FETCH_ASSOC,
			self::FETCH_BOTH	=> PDO::FETCH_BOTH,
			self::FETCH_NUM		=> PDO::FETCH_NUM,
		);
		
		return isset($conf[$self_fetch]) ? $conf[$self_fetch] : PDO::FETCH_ASSOC;
	}
	
	/**
	 * 处理PDO错误
	 * @param PDO $pdo	PDO对象
	 */
	public function error($pdo)
	{
		list($code, $sql_code, $info) = $pdo->errorInfo();
		$string = "({$code}) $info ($sql_code) [{$this->sql}]";
		
		$this->throwError($string);
	}	
}
